import fcgiClient from 'fastcgi-client';
import settle from 'axios/lib/core/settle';
import createError from 'axios/lib/core/createError';
import querystring from 'querystring';
import urlencode from 'urlencode-for-php';
import { PHP_DOCUMENT_ROOT, PHP_SCRIPT_NAME, PHP_SOCKET } from '$env';

/**
 * 创建 FastCGI 客户端 并常驻内存
 */
let createPromise;
let fastcgiClient;
const createFastCGI = () => {
  if (fastcgiClient) {
    return Promise.resolve(fastcgiClient);
  }
  if (createPromise) {
    return createPromise;
  }
  const client = fcgiClient({
    sockFile: PHP_SOCKET,
  });
  createPromise = new Promise((resolve, reject) => {
    client.on('ready', () => {
      fastcgiClient = client;
      createPromise = null;
      resolve(client);
    });
  });
  return createPromise;
};

/**
 * axios 请求代理
 * @param {object} config
 */
const request = (config) => {
  return new Promise(async (resolve, reject) => {
    const client = await createFastCGI();
    const method = config.method.toUpperCase();
    const query = config.params && querystring.stringify(config.params) || '';
    let content;
    if (config.data && method === 'POST' || method === 'PUT') {
      if (config.headers['Content-Type']) {
        if (config.headers['Content-Type'].indexOf('application/json') === 0) {
          content = Buffer.from(config.data, 'utf8');
        } else if (config.headers['Content-Type'].indexOf('application/x-www-form-urlencoded') === 0) {
          content = Buffer.from(urlencode(config.data), 'utf8');
        }
        config.headers['Content-Length'] = content.length;
      }
    }
    // params Should be FastCGI params (key-value pairs)
    const params = {
      QUERY_STRING: query,
      REQUEST_METHOD: method,
      CONTENT_TYPE: '',
      CONTENT_LENGTH: '',
      PATH_INFO: config.url,
      SCRIPT_FILENAME: `${PHP_DOCUMENT_ROOT}/${PHP_SCRIPT_NAME}`,
      SCRIPT_NAME: `/${PHP_SCRIPT_NAME}`,
      REQUEST_URI: query ? `${config.url}?${query}` : config.url,
      DOCUMENT_URI: `/${PHP_SCRIPT_NAME}`,
      DOCUMENT_ROOT: PHP_DOCUMENT_ROOT,
      SERVER_PROTOCOL: 'HTTP/1.1',
      GATEWAY_INTERFACE: 'CGI/1.1',
      REMOTE_ADDR: '8.8.8.8',
      REMOTE_PORT: 50000,
      SERVER_ADDR: '127.0.0.1',
      SERVER_PORT: 80,
      SERVER_NAME: '127.0.0.1',
      SERVER_SOFTWARE: 'NodeFastCGI/1.0.0',
      REDIRECT_STATUS: 200,
    };
    config.cgiParams.CONTENT_TYPE = config.headers['Content-Type'] || '';
    config.cgiParams.CONTENT_LENGTH = config.headers['Content-Length'] || '';
    Object.keys(config.headers).forEach((key) => {
      const headerName = key.toUpperCase().replace(/-/g, '_');
      params[`HTTP_${headerName}`] = config.headers[key];
    });
    Object.keys(config.cgiParams).forEach((key) => {
      params[key] = config.cgiParams[key];
    });
    // HTTP_X_FORWARDED_FOR
    // HTTP_ACCEPT_LANGUAGE
    // HTTP_ACCEPT_ENCODING
    // HTTP_USER_AGENT
    // HTTP_CONNECTION
    // HTTP_HOST
    client.request(params, (err, req) => {
      if (err) {
        // release current client
        fastcgiClient = null;
        reject(err);
      } else {
        let errors = '';
        let body = '';
        let headers = {};
        req.stdout.on('data', (data) => {
          body += data.toString('utf8');
        });
        req.stderr.on('data', (data) => {
          errors += data.toString('utf8');
        });
        req.stdout.on('end', () => {
          const headersString = body.match(/(^[\s\S]*?)\r\n\r\n/)[1];
          body = body.substr(headersString.length + 4); // remove headers and \r\n characters
          const headersArray = headersString.split('\r\n');
          const headersObj = {};
          headersArray.map((header) => {
            const delimiter = header.indexOf(':');
            return headersObj[header.substr(0, delimiter)] = header.substr(delimiter + 2);
          });
          headers = headersObj;
          const res = {
            status: headers.Status ? parseInt(headers.Status, 10) : 200,
            headers,
            body,
          };
          resolve({ err: false, res, errors });
        });
        if (content && method === 'POST' || method === 'PUT') {
          req.stdin._write(content, 'utf8');
        }
        req.stdin.end();
      }
    });
  });
};

export default (config) => {
  return new Promise((resolve, reject) => {
    request(config).then(({ err, res, errors }) => {
      const response = {
        data: res.body,
        status: res.status,
        statusText: res.body,
        headers: res.headers,
        config,
        request,
      };
      settle(resolve, reject, response);
    }).catch(() => {
      reject(createError('UNIX SOCK Error', config, null, request));
    });
  });
};
